/*
 * Copyright (c) 2007 Aleksey Nikiforov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Aleksey Nikiforov' nor the names of other contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.lex.input.effects.interpolation;

import org.lex.input.effects.component.SpringComponent;
import org.lex.input.effects.component.SpringConfig;

import com.jme.math.FastMath;
import com.jme.math.Vector3f;

/**
 * SpringInterpolation3f interpolates motion between two 3d points using spring
 * interpolation. The spring model used is 1 dimentional, so this class
 * will not calculate 3d momentum correctly when the destanation point
 * changes.
 * This class is only ment to be used for special effects, which work more
 * smoothly with 1 dimentional spring model rather than a full simulation
 * of a spring in 3d.
 * This class is by no means an accurate representation of a spring motion in 3d.
 * 
 * @author lex
 *
 */
public class SpringVector3fInterpolation
	extends SpringConfig
	implements GoalVector3fInterpolation
{

	private float initialSpringConstant;
	private Vector3f current = new Vector3f();
	private Vector3f goal = new Vector3f();
	
	private Vector3f temp = new Vector3f();
	
	private SpringComponent spring;
	private float maxSpeed;
	private float defaultMaxSpeed;
	private float snapDistance;
	private boolean acceleratePastInitial;
	private float accelerationFactor = 1000;
	
	private float distanceCountdown;
	private float timeCount;
	
	/**
	 * @param mass
	 * @param springConstant
	 * @param dampingConstant
	 */
	public SpringVector3fInterpolation(float mass,
					  float springConstant,
					  float dampingConstant)
	{
		super(mass, springConstant, dampingConstant);
		initialSpringConstant = springConstant;
		spring = SpringComponent.getForConfig(this);
		passOverAllowed = false;
	}
	
	public void update(float deltaT) {
		Vector3f direction = goal.subtract(current, temp);
		
		float length = direction.length();
		if (length > FastMath.ZERO_TOLERANCE) {
			if (acceleratePastInitial) {
				if(length <= snapDistance) distanceCountdown = 0;
				if (distanceCountdown <= 0) {
					maxSpeed = 0;
					timeCount += deltaT;
					k = initialSpringConstant*FastMath.pow(
							accelerationFactor, timeCount);
				}
			}
			spring.setDisplacement(length);
			spring.update(deltaT);
			
			// this is always positive because spring displacement is length-based
			// and legnth is always positive
			float distanceChange = spring.getTravelVector();
			if (maxSpeed != 0 && Math.abs(distanceChange/deltaT) > maxSpeed) {
	        	distanceChange = maxSpeed*deltaT;
	        }
			
			direction.normalizeLocal();
			direction.multLocal(length - distanceChange);
			goal.subtract(direction, current);
			
			distanceCountdown -= distanceChange;
		} else {
			spring.stop();
		}
		
		if (!isAlive()) {
			current.set(goal);
		}
	}

	public void clear() {
		current.set(goal);
		spring.stop();
		distanceCountdown = 0;
		k = initialSpringConstant;
		timeCount = 0;
		maxSpeed = defaultMaxSpeed;
	}

	/**
	 * @see org.lex.input.effects.interpolation.GoalVector3fInterpolation#init(com.jme.math.Vector3f, com.jme.math.Vector3f)
	 */
	public void init(Vector3f current, Vector3f goal) {
		this.current.set(current);
		this.goal.set(goal);
		spring.stop();
		distanceCountdown = goal.subtract(current, temp).length();
		k = initialSpringConstant;
		timeCount = 0;
		maxSpeed = defaultMaxSpeed;
	}

	public Vector3f getCurrent() {
		return current;
	}

	public void setGoal(Vector3f goal) {
		this.goal.set(goal);
	}

	public void setCurrent(Vector3f current) {
		this.current.set(current);
	}
	
	public boolean isAlive() {
		return (spring.getDisplacement() != 0);
	}

	public boolean isAcceleratingPastInitial() {
		return acceleratePastInitial;
	}

	public void setAcceleratePastInitial(boolean accelerate) {
		acceleratePastInitial = accelerate;
	}

	public float getSnapDistance() {
		return snapDistance;
	}

	/**
	 * @see org.lex.input.effects.interpolation.ClampedInterpolation#setSnapDistance(float)
	 */
	public void setSnapDistance(float snapDistance) {
		this.snapDistance = snapDistance;
	}
	
	public float getAccelerationFactor() {
		return accelerationFactor ;
	}

	public void setAccelerationFactor(float factor) {
		accelerationFactor = factor;
	}
	
	public float getMaxSpeed() {
		return defaultMaxSpeed;
	}

	public void setMaxSpeed(float maxSpeed) {
		defaultMaxSpeed = maxSpeed;
		if (distanceCountdown > 0) this.maxSpeed = maxSpeed;
	}
	
}
